package com.rdzn.mall.portal.config;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.rdzn.mall.portal.domain.UmsMemberDO;
import com.rdzn.mall.portal.domain.WxUserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author zhigangding
 * @date 2019/12/2
 */
@Component
public class JwtConfig {

    /**
     * JWT 自定义密钥 我这里写死的
     */
    @Value("${jwt.secret}")
    private String SECRET_KEY ;

    /**
     * JWT 过期时间值
     */
    @Value("${jwt.expiration}")
    private Long expire_time;

    public static final String tokenHead = "Bearer ";

    @Autowired
    private StringRedisTemplate redisTemplate;

    public static final String wxOpenId = "wxOpenId";
    public static final String sessionKey = "sessionKey";
    public static final String jwt_id = "jwt-id";
    public static final String JWT_SESSION_ = "JWT-SESSION-";

    /**
     * 根据微信用户登陆信息创建 token
     * 注 : 这里的token会被缓存到redis中,用作为二次验证
     * redis里面缓存的时间应该和jwt token的过期时间设置相同
     *
     * @param umsMemberDO 微信用户信息
     * @return 返回 jwt token
     */
    public String createTokenByWxAccount(WxUserInfo umsMemberDO) {
        //JWT 随机ID,做为验证的key
        String jwtId = UUID.randomUUID().toString();
        //1 . 加密算法进行签名得到token
        Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
        String token = JWT.create()
                .withClaim(wxOpenId, umsMemberDO.getWeixinOpenid())
                .withClaim(sessionKey, umsMemberDO.getSessionKey())
                .withClaim(jwt_id, jwtId)
                //JWT 配置过期时间的正确姿势
                .withExpiresAt(new Date(System.currentTimeMillis() + expire_time*1000))
                .sign(algorithm);
        //2 . Redis缓存JWT, 注 : 请和JWT过期时间一致
        redisTemplate.opsForValue().set(JWT_SESSION_ + jwtId, token, expire_time, TimeUnit.SECONDS);
        return token;
    }

    /**
     * 校验token是否正确
     * 1 . 根据token解密，解密出jwt-id , 先从redis中查找出redisToken，匹配是否相同
     * 2 . 然后再对redisToken进行解密，解密成功则 继续流程 和 进行token续期
     *
     * @param token 密钥
     * @return 返回是否校验通过
     */
    public boolean verifyToken(String token) {
//        try {
            //1 . 根据token解密，解密出jwt-id , 先从redis中查找出redisToken，匹配是否相同
            String redisToken = redisTemplate.opsForValue().get(JWT_SESSION_ + getJwtIdByToken(token));
            if (redisToken == null || !redisToken.equals(token)){
                return false;
            }
            if (redisToken.contains(tokenHead)){
                // The part after "Bearer "
                redisToken = redisToken.substring(tokenHead.length());
            }
            //2 . 得到算法相同的JWTVerifier
            Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim(wxOpenId, getWxOpenIdByToken(redisToken))
                    .withClaim(sessionKey, getSessionKeyByToken(redisToken))
                    .withClaim(jwt_id, getJwtIdByToken(redisToken))
                    //JWT 正确的配置续期姿势
                    .acceptExpiresAt(System.currentTimeMillis() + expire_time*1000 )
                    .build();
            //3 . 验证token
            verifier.verify(redisToken);
            //4 . Redis缓存JWT续期
            redisTemplate.opsForValue().set(JWT_SESSION_ + getJwtIdByToken(token), redisToken, expire_time, TimeUnit.SECONDS);
            return true;
//        } catch (Exception e) { //捕捉到任何异常都视为校验失败
//            throw new AuthenticationException(e.getMessage());
////            return false;
//        }
    }

    /**
     * 根据Token获取wxOpenId(注意坑点 : 就算token不正确，也有可能解密出wxOpenId,同下)
     */
    public String getWxOpenIdByToken(String token) throws JWTDecodeException {
        return JWT.decode(token).getClaim(wxOpenId).asString();
    }

    /**
     * 根据Token获取sessionKey
     */
    public String getSessionKeyByToken(String token) throws JWTDecodeException {
        return JWT.decode(token).getClaim(sessionKey).asString();
    }

    /**
     * 根据Token 获取jwt-id
     */
    private String getJwtIdByToken(String token) throws JWTDecodeException {
        return JWT.decode(token).getClaim(jwt_id).asString();
    }
}
